/////////////////////////////////////////////////////////////////////////////////
// Math Library : Matrix 4x4												   //
// Purpose: A templated class for use of a matrix sized 4x4					   //
// Author: Roberto Rubiano													   //
/////////////////////////////////////////////////////////////////////////////////

#ifndef MATRIX4X4_H
#define MATRIX4X4_H

#include <math.h>
#include "vector3.h"

template<typename type>
struct Matrix4x4
{

	//////////////////////////////////////////////////////////////////
	// Member Variables												//
	//////////////////////////////////////////////////////////////////
	union
	{
		// access the matrix with a two dimensional array
		type mat[4][4];

		// access the matrix with a single array
		type matrix[16];

		struct
		{
			type xx, xy, xz, xw;
			type yx, yy, yz, yw;
			type zx, zy, zz, zw;
			type wx, wy, wz, ww;
		};

		struct
		{
			// access the matrix by a specific axis
			Vector3<type> xAxis;  type pad_xw;
			Vector3<type> yAxis;  type pad_yw;
			Vector3<type> zAxis;  type pad_zw;

			union
			{
				struct
				{
					Vector3<type> wAxis; type pad_ww;
				};

				struct
				{
					// Matrix's position
					Vector3<type> position;
				};
			};
		};
	};

	// Constructor
	Matrix4x4() {}
	// Destructor
	~Matrix4x4() {}
	// Copy Constructor
	template<typename type>
	Matrix4x4(const Matrix4x4<type>& matrix) : xx(matrix.xx), xy(matrix.xy), xz(matrix.xz), xw(matrix.xw),
											   yx(matrix.yx), yy(matrix.yy), yz(matrix.yz), yw(matrix.yw),
											   zx(matrix.zx), zy(matrix.zy), zz(matrix.zz), zw(matrix.zw),
											   wx(matrix.wx), wy(matrix.wy), wz(matrix.wz), ww(matrix.ww) {}

	// Constructor that takes one type
	Matrix4x4(type Component)  : xx(Component), xy(Component), xz(Component), xw(Component),
							     yx(Component), yy(Component), yz(Component), yw(Component),
								 zx(Component), zy(Component), zz(Component), zw(Component),
							     wx(Component), wy(Component), wz(Component), ww(Component) {}

	// Constructor that takes 16 types
	Matrix4x4(type ComponentXX, type ComponentXY, type ComponentXZ, type ComponentXW,
			  type ComponentYX, type ComponentYY, type ComponentYZ, type ComponentYW,
			  type ComponentZX, type ComponentZY, type ComponentZZ, type ComponentZW,
			  type ComponentWX, type ComponentWY, type ComponentWZ, type ComponentWW) :
			  xx(ComponentXX), xy(ComponentXY), xz(ComponentXZ), xw(ComponentXW),
			  yx(ComponentYX), yy(ComponentYY), yz(ComponentYZ), yw(ComponentYW),
			  zx(ComponentZX), zy(ComponentZY), zz(ComponentZZ), zw(ComponentZW),
			  wx(ComponentWX), wy(ComponentWY), wz(ComponentZW), ww(ComponentWW) {}


	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// Function : friend void MatrixMultiply(Matrix4x4<type> out, Matrix4x4<type> MatrixA, Matrix4x4<type> MatrixB) //
	// Purpose : Multiplies two Matricies																			//
	// Extra : friend function so that it can be called from anywhere, fills out the first Matrix passed in		    //
	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	friend void MatrixMultiply(Matrix4x4<type> out, Matrix4x4<type> MatrixA, Matrix4x4<type> MatrixB)
	{
		out.xx = MatrixB.xx * MatrixA.xx + MatrixB.yx * MatrixA.xy + MatrixB.zx * MatrixA.xz + MatrixB.wx * MatrixA.xw;
		out.xy = MatrixB.xy * MatrixA.xx + MatrixB.yy * MatrixA.xy + MatrixB.zy * MatrixA.xz + MatrixB.wy * MatrixA.xw;
		out.xz = MatrixB.xz * MatrixA.xx + MatrixB.yz * MatrixA.xy + MatrixB.zz * MatrixA.xz + MatrixB.wz * MatrixA.xw;
		out.xw = MatrixB.xw * MatrixA.xx + MatrixB.yw * MatrixA.xy + MatrixB.zw * MatrixA.xz + MatrixB.ww * MatrixA.xw;
		
		out.yx = MatrixB.xx * MatrixA.yx + MatrixB.yx * MatrixA.yy + MatrixB.zx * MatrixA.yz + MatrixB.wx * MatrixA.yw;
		out.yy = MatrixB.xy * MatrixA.yx + MatrixB.yy * MatrixA.yy + MatrixB.zy * MatrixA.yz + MatrixB.wy * MatrixA.yw;
		out.yz = MatrixB.xz * MatrixA.yx + MatrixB.yz * MatrixA.yy + MatrixB.zz * MatrixA.yz + MatrixB.wz * MatrixA.yw;
		out.yw = MatrixB.xw * MatrixA.yx + MatrixB.yw * MatrixA.yy + MatrixB.zw * MatrixA.yz + MatrixB.ww * MatrixA.yw;
		
		out.zx = MatrixB.xx * MatrixA.zx + MatrixB.yx * MatrixA.zy + MatrixB.zx * MatrixA.zz + MatrixB.wx * MatrixA.zw;
		out.zy = MatrixB.xy * MatrixA.zx + MatrixB.yy * MatrixA.zy + MatrixB.zy * MatrixA.zz + MatrixB.wy * MatrixA.zw;
		out.zz = MatrixB.xz * MatrixA.zx + MatrixB.yz * MatrixA.zy + MatrixB.zz * MatrixA.zz + MatrixB.wz * MatrixA.zw;
		out.zw = MatrixB.xw * MatrixA.zx + MatrixB.yw * MatrixA.zy + MatrixB.zw * MatrixA.zz + MatrixB.ww * MatrixA.zw;
		
		out.wx = MatrixB.xx * MatrixA.wx + MatrixB.yx * MatrixA.wy + MatrixB.zx * MatrixA.wz + MatrixB.wx * MatrixA.ww;
		out.wy = MatrixB.xy * MatrixA.wx + MatrixB.yy * MatrixA.wy + MatrixB.zy * MatrixA.wz + MatrixB.wy * MatrixA.ww;
		out.wz = MatrixB.xz * MatrixA.wx + MatrixB.yz * MatrixA.wy + MatrixB.zz * MatrixA.wz + MatrixB.wz * MatrixA.ww;
		out.ww = MatrixB.xw * MatrixA.wx + MatrixB.yw * MatrixA.wy + MatrixB.zw * MatrixA.wz + MatrixB.ww * MatrixA.ww;
	}

	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// Function : friend void MatrixMultiply(Matrix4x4<type> out, Matrix4x4<type> MatrixA, Matrix4x4<type> MatrixB) //
	// Purpose : Multiplies two Matricies																			//
	// Extra : friend function so that it can be called from anywhere, return a filled out matrix				    //
	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	friend Matrix4x4<type> MatrixMultiply(Matrix4x4<type> MatrixA, Matrix4x4<type> MatrixB)
	{
		Matrix4x4<type> out;

		out.xx = MatrixB.xx * MatrixA.xx + MatrixB.yx * MatrixA.xy + MatrixB.zx * MatrixA.xz + MatrixB.wx * MatrixA.xw;
		out.xy = MatrixB.xy * MatrixA.xx + MatrixB.yy * MatrixA.xy + MatrixB.zy * MatrixA.xz + MatrixB.wy * MatrixA.xw;
		out.xz = MatrixB.xz * MatrixA.xx + MatrixB.yz * MatrixA.xy + MatrixB.zz * MatrixA.xz + MatrixB.wz * MatrixA.xw;
		out.xw = MatrixB.xw * MatrixA.xx + MatrixB.yw * MatrixA.xy + MatrixB.zw * MatrixA.xz + MatrixB.ww * MatrixA.xw;
		
		out.yx = MatrixB.xx * MatrixA.yx + MatrixB.yx * MatrixA.yy + MatrixB.zx * MatrixA.yz + MatrixB.wx * MatrixA.yw;
		out.yy = MatrixB.xy * MatrixA.yx + MatrixB.yy * MatrixA.yy + MatrixB.zy * MatrixA.yz + MatrixB.wy * MatrixA.yw;
		out.yz = MatrixB.xz * MatrixA.yx + MatrixB.yz * MatrixA.yy + MatrixB.zz * MatrixA.yz + MatrixB.wz * MatrixA.yw;
		out.yw = MatrixB.xw * MatrixA.yx + MatrixB.yw * MatrixA.yy + MatrixB.zw * MatrixA.yz + MatrixB.ww * MatrixA.yw;
		
		out.zx = MatrixB.xx * MatrixA.zx + MatrixB.yx * MatrixA.zy + MatrixB.zx * MatrixA.zz + MatrixB.wx * MatrixA.zw;
		out.zy = MatrixB.xy * MatrixA.zx + MatrixB.yy * MatrixA.zy + MatrixB.zy * MatrixA.zz + MatrixB.wy * MatrixA.zw;
		out.zz = MatrixB.xz * MatrixA.zx + MatrixB.yz * MatrixA.zy + MatrixB.zz * MatrixA.zz + MatrixB.wz * MatrixA.zw;
		out.zw = MatrixB.xw * MatrixA.zx + MatrixB.yw * MatrixA.zy + MatrixB.zw * MatrixA.zz + MatrixB.ww * MatrixA.zw;
		
		out.wx = MatrixB.xx * MatrixA.wx + MatrixB.yx * MatrixA.wy + MatrixB.zx * MatrixA.wz + MatrixB.wx * MatrixA.ww;
		out.wy = MatrixB.xy * MatrixA.wx + MatrixB.yy * MatrixA.wy + MatrixB.zy * MatrixA.wz + MatrixB.wy * MatrixA.ww;
		out.wz = MatrixB.xz * MatrixA.wx + MatrixB.yz * MatrixA.wy + MatrixB.zz * MatrixA.wz + MatrixB.wz * MatrixA.ww;
		out.ww = MatrixB.xw * MatrixA.wx + MatrixB.yw * MatrixA.wy + MatrixB.zw * MatrixA.wz + MatrixB.ww * MatrixA.ww;
	
		return out;
	}

	friend void TranslateMatrix(void);
	friend void ScaleMatrix(void);
	friend void RotateMatrix(void);

	//////////////////////////////////////
	// Function : void MakeZero(void)   //
	// Purpose : Zeros out this matrix  //
	//////////////////////////////////////
	void MakeZero(void)
	{
		for(int i = 0; i < 16; i++)
		{
			matrix[i] = 0;
		}
	}

	////////////////////////////////////////////
	// Function : void MakeIdentity(void)     //
	// Purpose : make this an identity matrix //
	////////////////////////////////////////////
	void MakeIdentity(void)
	{
		xx = 1; xy = 0; xz = 0; xw = 0;
		yx = 0; yy = 1; yz = 0; yw = 0;
		zx = 0; zy = 0; zz = 1; zw = 0;
		wx = 0; wy = 0; wz = 0; ww = 1;
	}

	///////////////////////////////////////////////////////////
	// Function : void MakeTranslate(type x, type y, type z) //
	// Purpose : Makes this a translation Matrix			 //
	// Extra : takes in 3 type object						 //
	///////////////////////////////////////////////////////////
	void MakeTranslate(type x, type y, type z)
	{
		xx = 1; xy = 0; xz = 0; xw = x;
		yx = 0; yy = 1; yz = 0; yw = y;
		zx = 0; zy = 0; zz = 1; zw = z;
		wx = 0; wy = 0; wz = 0; ww = 1;
	}

	///////////////////////////////////////////////////////////
	// Function : void MakeTranslate(Vector3<type> vector)   //
	// Purpose : Makes this a translation Matrix			 //
	// Extra : takes in a vector 3							 //
	///////////////////////////////////////////////////////////
	void MakeTraknslate(Vector3<type> vector)
	{
		xx = 1; xy = 0; xz = 0; xw = vector.x;
		yx = 0; yy = 1; yz = 0; yw = vector.y;
		zx = 0; zy = 0; zz = 1; zw = vector.z;
		wx = 0; wy = 0; wz = 0; ww = 1;
	}

	///////////////////////////////////////////////////////
	// Function : void MakeScale(type x, type y, type z) //
	// Purpose : makes this a scale matrix				 //
	// Extra : takes in 3 type object					 //
	///////////////////////////////////////////////////////
	void MakeScale(type x, type y, type z)
	{
		xx = x; xy = 0; xz = 0; xw = 0;
		yx = 0; yy = y; yz = 0; yw = 0;
		zx = 0; zy = 0; zz = z; zw = 0;
		wx = 0; wy = 0; wz = 0; ww = 1;
	}

	////////////////////////////////////////////////////////////
	// Function : void MakeScale(Vector3<type> vector)        //
	// Purpose : makes this a scale matrix					  //
	// Extra : takes in a vector 3							  //
	////////////////////////////////////////////////////////////
	void MakeScale(Vector3<type> vector)
	{
		xx = vector.x; xy = 0;        xz = 0;        xw = 0;
		yx = 0;		   yy = vector.y; yz = 0;        yw = 0;
		zx = 0;        zy = 0;        zz = vector.z; zw = 0;
		wx = 0;        wy = 0;        wz = 0;        ww = 1;
	}

	///////////////////////////////////////////////////////////////
	// Function : void MakeRotationX(type degree)				 //
	// Purpose : Make this an x rotation matrix					 //
	///////////////////////////////////////////////////////////////
	void MakeRotationX(type degree)
	{
		xx = 1; xy = 0;			        xz = 0;					   xw = 0;
		yx = 0; yy = (type)cos(degree); yz = (type)-sin(degree);   yw = 0;
		zx = 0;	zy = (type)sin(degree); zz = (type)cos(degree);    zw = 0;
		wx = 0; wy = 0;			        wz = 0;					   ww = 1;
	}

	///////////////////////////////////////////////////////////////
	// Function : void MakeRotationY(type degree)				 //
	// Purpose : Make this a y rotation matrix					 //
	///////////////////////////////////////////////////////////////
	void MakeRotationY(type degree)
	{
		xx = (type)cos(degree);  xy = 0; xz = (type)sin(degree); xw = 0;
		yx = 0;			         yy = 1; yz = 0;			     yw = 0;
		zx = (type)-sin(degree); zy = 0; zz = (type)cos(degree); zw = 0;
		wx = 0;			         wy = 0; wz = 0;			     ww = 1;
	}

	///////////////////////////////////////////////////////////////
	// Function : void MakeRotationZ(type degree)				 //
	// Purpose : Make this a z rotation matrix					 //
	///////////////////////////////////////////////////////////////
	void MakeRotationZ(type degree)
	{
		xx = (type)cos(degree);  xy = (type)-sin(degree); xz = 0; xw = 0;
		yx = (type)sin(degree);  yy = (type)cos(degree);  yz = 0; yw = 0;
		zx = 0;                  zy = 0;                  zz = 1; zw = 0;
		wx = 0;                  wy = 0;                  wz = 0; ww = 1;
	}

	type getDeterminant(void)
	{

	}

	void MakeInverse(void)
	{
		Matrix4x4<type> Inv;
		
		


	}

	//////////////////////////////////////
	// Function : void Transpose(void)  //
	// Purpose : transposes this matrix //
	//////////////////////////////////////
	void Transpose(void)
	{
		std::swap(xy, yx);
		std::swap(xz, zx);
		std::swap(xw, wx);	
		std::swap(yz, zy);
		std::swap(yw, wy);
		std::swap(zw, wz);
	}

	///////////////////////////////////////////////////
	// Overloaded Operators							 //
	///////////////////////////////////////////////////

	// Assignment Operator
	template<typename type>
	void operator=(Matrix4x4<type> item)
	{
		for(int i = 0; i < 16; i++)
		{
			matrix[i] = item.matrix[i];
		}
	}

	///////////////////////////////////////////////////
	// Matrix to Matrix comparision operators		 //
	///////////////////////////////////////////////////

	template<typename type>
	bool operator==(Matrix4x4<type> item)
	{
		for(int i = 0; i < 16; i++)
		{
			if(matrix[i] == item.matrix[i])
				continue;
			else 
				return false;
		}

		return true;
	}

	template<typename type>
	bool operator>(Matrix4x4<type> item)
	{
		for(int i = 0; i < 16; i++)
		{
			if(matrix[i] > item.matrix[i])
				continue;
			else
				return false;
		}

		return true;
	}

	template<typename type>
	bool operator>=(Matrix4x4<type> item)
	{
		bool check = false;
		
		for(int i = 0; i < 16; i++)
		{
			if(matrix[i] > item.matrix[i])
				continue;
			else 
			{
				check = true;
				break;
			}
		}

		if(check == true)
		{
			for(int i = 0; i < 16; i++)
			{
				if(matrix[i] == item.matrix[i])
					continue;
				else 
					return false;
			}
		}

		return true;
	}

	template<typename type>
	bool operator<(Matrix4x4<type> item)
	{
		for(int i = 0; i < 16; i++)
		{
			if(matrix[i] < item.matrix[i])
				continue;
			else
				return false;
		}

		return true;
	}

	template<typename type>
	bool operator<=(Matrix4x4<type> item)
	{
		bool check = false;
		
		for(int i = 0; i < 16; i++)
		{
			if(matrix[i] < item.matrix[i])
				continue;
			else 
			{
				check = true;
				break;
			}
		}

		if(check == true)
		{
			for(int i = 0; i < 16; i++)
			{
				if(matrix[i] == item.matrix[i])
					continue;
				else 
					return false;
			}
		}

		return true;
	}

	////////////////////////////////////////////////////////////
	// Matrix to Matrix math operators						  //
	////////////////////////////////////////////////////////////
	template<typename type>
	Matrix4x4<type> operator+(Matrix4x4<type> item)
	{
		Matrix4x4<type> out;

		for(int i = 0; i < 16; i++)
		{
			out.matrix[i] = matrix[i] + item.matrix[i];
		}

		return out;
	}

	template<typename type>
	void operator+=(Matrix4x4<type> item)
	{
		for(int i = 0; i < 16; i++)
		{
			matrix[i] += item.matrix[i];
		}
	}

	template<typename type>
	Matrix4x4<type> operator-(Matrix4x4<type> item)
	{
		Matrix4x4<type> out;

		for(int i = 0; i < 16; i++)
		{
			out.matrix[i] = matrix[i] - item.matrix[i];
		}

		return out;
	}

	template<typename type>
	void operator-=(Matrix4x4<type> item)
	{
		for(int i = 0; i < 16; i++)
		{
			matrix[i] -= item.matrix[i];
		}
	}

	//////////////////////////////////////////////////////////
	// Matrix to "type" math operations						//
	//////////////////////////////////////////////////////////
	template<typename type>
	Matrix4x4<type> operator+(type item)
	{
		Matrix4x4<type> out;

		for(int i = 0; i < 16; i++)
		{
			out.matrix[i] = matrix[i] + item;
		}

		return out;
	}

	template<typename type>
	void operator+=(type item)
	{
		for(int i = 0; i < 16; i++)
		{
			matrix[i] += item;
		}
	}

	template<typename type>
	Matrix4x4<type> operator-(type item)
	{
		Matrix4x4<type> out;

		for(int i = 0; i < 16; i++)
		{
			out.matrix[i] = matrix[i] - item.matrix[i];
		}

		return out;
	}

	template<typename type>
	void operator-=(type item)
	{
		for(int i = 0; i < 16; i++)
		{
			matrix[i] -= item;
		}
	}

	template<typename type>
	Matrix4x4<type> operator*(type item)
	{
		Matrix4x4<type> out;

		for(int i = 0; i < 16; i++)
		{
			out.matrix[i] = matrix[i] * item.matrix[i];
		}

		return out;
	}

	template<typename type>
	void operator*=(type item)
	{
		for(int i = 0; i < 16; i++)
		{
			matrix[i] *= item;
		}
	}

	template<typename type>
	Matrix4x4<type> operator/(type item)
	{
		Matrix4x4<type> out;

		for(int i = 0; i < 16; i++)
		{
			out.matrix[i] = matrix[i] / item.matrix[i];
		}

		return out;
	}

	template<typename type>
	void operator/=(type item)
	{
		for(int i = 0; i < 16; i++)
		{
			matrix[i] /= item;
		}
	}

};

typedef Matrix4x4<float> mat4f;

#endif